﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using JetBrains.Annotations;
using StreetPacMan.Server.Facade;
using StreetPacMan.Server.HardcodedGameData;

namespace StreetPacMan.Server
{
    public interface IStatusMessageGenerator
    {
        ServerPollResult CreateUpdateMessageFor(int entityId, int sendUpdatesForTheLastXSeconds);
        GetGameStateResult CreateInitialGameMessagesFor(int entityId);
    }

    [UsedByIoc]
    public class StatusMessageGenerator : IStatusMessageGenerator
    {
        private readonly IConnectionProvider _ConnectionProvider;

        public StatusMessageGenerator(IConnectionProvider connectionProvider)
        {
            _ConnectionProvider = connectionProvider;
        }

        public ServerPollResult CreateUpdateMessageFor(int entityId, int sendUpdatesForTheLastXSeconds)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                var dataLoadOptions = new DataLoadOptions();
                dataLoadOptions.LoadWith<Player>(x => x.Game);
                pacManDataContext.LoadOptions = dataLoadOptions;

                var player = pacManDataContext.Players.Where(x => x.Id == entityId).FirstOrDefault();
                if(player==null)
                {
                    throw new ArgumentException(string.Format("Player id '{0}' does not exist", entityId));
                }

                var clientEntities =
                    GetVisiblePlayersFor(player)
                    .Select(x => new ClientEntity(x.Id, x.Name, (PlayerRole)x.Role, x.LastGeoLat, x.LastGeoLon))
                    .ToArray();

                var pacMenLeft =
                    player.Game.Players.Where(x => x.State != (int) PlayerState.JoinedEatenNotPlaying).Count();

                return new ServerPollResult(
                    clientEntities,
                    GetAllAppleIdsRemovedInTheLastXSeconds(player.Game, sendUpdatesForTheLastXSeconds),
                    GetAllMessagesSentInTheLastXSeconds(player.Game, sendUpdatesForTheLastXSeconds),
                    (GameState)player.Game.State,
                    player.Game.Score,
                    pacMenLeft);
            }
        }

        private ClientMessage[] GetAllMessagesSentInTheLastXSeconds(Game game, int lastSeconds)
        {
            var lastTimeToReport = DateTime.Now.AddSeconds(-lastSeconds);
            return game.Messages
                .Where(x =>  x.At > lastTimeToReport)
                .Select(x => new ClientMessage(x.Id, game.Players.Where(y=>y.Id==x.SenderId).Select(y=>y.Name).FirstOrDefault(), x.At,x.Text))
                .ToArray();
        }
        private int[] GetAllAppleIdsRemovedInTheLastXSeconds(Game game, int lastSeconds)
        {
            var lastTimeToReport = DateTime.Now.AddSeconds(-lastSeconds);
            return game.Apples
                .Where(x => x.State == (int)AppleState.RemovedEaten && x.RemovedAt > lastTimeToReport)
                .Select(x => x.Id)
                .ToArray();
        }
        public GetGameStateResult CreateInitialGameMessagesFor(int entityId)
        {
            var geoArray = MasarikGeos.GetBoardParimeter();

            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                var dataLoadOptions = new DataLoadOptions();
                dataLoadOptions.LoadWith<Player>(x => x.Game);
                pacManDataContext.LoadOptions = dataLoadOptions;

                var requestingUser = pacManDataContext.Players.Where(x => x.Id == entityId).FirstOrDefault();
                if (requestingUser == null)
                {
                    throw new ArgumentException(string.Format("Player id '{0}' does not exist", entityId));
                }

                var game = requestingUser.Game;
                if (requestingUser.RoleEnum == PlayerRole.Ghost)
                {
                    return new GetGameStateResult(
                        game.StateEnum, 
                        game.Score,
                        game.Messages.OrderBy(x => x.Id).Last().Text,
                        game.Players.Select(x=>new PlayerDescription(x.Name,x.RoleEnum)).ToArray(),
                        new ClientApple[0], 
                        geoArray);
                }

                var apples = game
                    .Apples
                    .Where(x => x.State == (int)AppleState.Active)
                    .Select(x => new ClientApple(x.Id, x.KindEnum, x.GeoLat, x.GeoLon))
                    .ToArray();

                return new GetGameStateResult(
                    game.StateEnum,
                    game.Score,
                    game.Messages.OrderBy(x => x.Id).Last().Text,
                    game.Players.Select(x => new PlayerDescription(x.Name, x.RoleEnum)).ToArray(),
                    apples,
                    geoArray);
            }
        }

        private IEnumerable<Player> GetVisiblePlayersFor(Player entity)
        {
            var allPlayersInGame = entity
                .Game
                .Players
                .ToArray();
            if (entity.RoleEnum == PlayerRole.Observer)
            {
                return allPlayersInGame;
            }

            var allActivePlayersInGame = allPlayersInGame
                .Where(x => (PlayerRole)x.Role == PlayerRole.PacMan || (PlayerRole)x.Role == PlayerRole.Ghost)
                .Where(x => x.State == (int)PlayerState.JoinedActivePlaying)
                .ToArray();
            if (entity.RoleEnum == PlayerRole.PacMan)
            {
                return allActivePlayersInGame;
            }

            if (entity.RoleEnum == PlayerRole.Ghost)
            {
                return allActivePlayersInGame
                    .Where(x => (PlayerRole)x.Role == PlayerRole.PacMan)
                    .ToArray() //required here since the linq2sql query cannot take the method call to the db
                    .Where(x => TwoGeos.GetDistance(x.LastGeoLat, x.LastGeoLon, entity.LastGeoLat, entity.LastGeoLon)< 50)
                    .FollowedByX(entity)
                    .ToArray();
            }

            return new Player[0];
        }
    }
}